¿Quiénes somos?

RLadies y Data Scientists en Kernel Analytics

Código y datos en https://github.com/intiveda/rladies_textmining

Text Mining & NLP

Gran parte de los datos se encuentran no estructurados, es importante conocer técnicas que nos permitan obtener conclusiones a partir de los mensajes que generan nuestras organizaciones, clientes o usuarios.

Hoy aprenderemos algunas técnicas básicas para manipular cadenas de texto y aplicaremos técnicas de NLP a subtítulos para obtener algunas conclusiones.

Materiales y librerías

Puedes seguir este tutorial de dos formas:

  1. Escuchando activamente, para probar luego en casa :)
  2. De forma activa, ejecutando en tu equipo:
c("stringr","subtools","tm","tidytext", "tidyverse","dplyr","data.table","ggplot2","plotly","igraph","ggraph","worldcloud","knit") %in% rownames(installed.packages())
 [1]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE FALSE

Back2basics: Strings y expresiones regulares

Las cadenas o strings cumplen un papel importante en las tareas de ETL o preparación de los datos. Una de las librerías esenciales en la materia es string

Stringr es uno de los paquetes diseñados por Hadley Wickham para asistir en las tareas de manipulación de strings:

¿Qué son las cadenas de texto o strings?

  • Las cadenas de texto se encuentran contenidas entre comillas "" (*usar la comilla simple ' para escapar la doble comilla)
  • Pueden contener letras "a", números "1", simbolos "&" o todo lo anterior "1a&"
  • Mientras que los números puede ser a la vez integers y characters, las letras y los símbolos no tienen significado como integer y se traducen en NA
as.integer(c("a", "&", "123"))
[1]  NA  NA 123
c(factor("a"), "b", "&",1)
[1] "1" "b" "&" "1"

Concatenar integers y characters, convierte automáticamente los integers en characters.

c(as.character(factor("a")), "b", "&",1)
[1] "a" "b" "&" "1"

Operaciones básicas

Importar la librería

# install.packages("stringr")
library(stringr)

Operadores

Muchas de estas funciones tienne su equivalente en R base, pueden ser más lentas/menos eficientes

  • str_to_upper(string): convierte un string en mayúsculas
  • str_to_lower(string): convierte un string en minúsculas
  • str_to_title(string): capitaliza un string
temas <- c("Código", "Mujeres", "tecnología", "Informática", "estadística", "Women", "Coders", "Aprendizaje", "automático", "Análisis", "datos", "Visualización", "R-Ladies", "Social", "Coding", "R", "Ciencia", "Programming")
str_to_upper(temas)
 [1] "CÓDIGO"        "MUJERES"       "TECNOLOGÍA"    "INFORMÁTICA"   "ESTADÍSTICA"   "WOMEN"        
 [7] "CODERS"        "APRENDIZAJE"   "AUTOMÁTICO"    "ANÁLISIS"      "DATOS"         "VISUALIZACIÓN"
[13] "R-LADIES"      "SOCIAL"        "CODING"        "R"             "CIENCIA"       "PROGRAMMING"  
str_to_lower(temas)
 [1] "código"        "mujeres"       "tecnología"    "informática"   "estadística"   "women"        
 [7] "coders"        "aprendizaje"   "automático"    "análisis"      "datos"         "visualización"
[13] "r-ladies"      "social"        "coding"        "r"             "ciencia"       "programming"  
str_to_title(temas)
 [1] "Código"        "Mujeres"       "Tecnología"    "Informática"   "Estadística"   "Women"        
 [7] "Coders"        "Aprendizaje"   "Automático"    "Análisis"      "Datos"         "Visualización"
[13] "R-Ladies"      "Social"        "Coding"        "R"             "Ciencia"       "Programming"  
  • str_c(string, sep = ""): junta varios string en uno solo, es el equivalente a paste(sep = "") o paste0()
  • str_length(string): devuelve la longitud del string, es similar a la función nchar(). Convierte los factores en strings y conserva los NA’s
print(str_length('R-Ladies'))
[1] 8
print(str_length(NA))
[1] NA
  • str_sub(string, start, end): subsetea un string o un vector de string especificando la posición inicial y la final, es el equivalente en R base a substr(). Por defecto finaliza en el último caracter.
print(temas[1:4])
[1] "Código"      "Mujeres"     "tecnología"  "Informática"
str_sub(string = temas[1:4], start=3)
[1] "digo"      "jeres"     "cnología"  "formática"
  • str_dup(string, times): copia y pega un string un número determinado de veces
str_dup(string = temas[1:4], times = 3)
[1] "CódigoCódigoCódigo"                "MujeresMujeresMujeres"            
[3] "tecnologíatecnologíatecnología"    "InformáticaInformáticaInformática"
  • str_trim(string, side = c("both", "left", "rigth")): elimina los espacios vacíos, por defecto toma el valor both. Mejor evitar gsub(" ", "", string)

  • str_pad(string, width, side = c("left", "both", "right"), pad = " ")): añade a strings espacios en blanco para igualarlos en longitud, especialmente útil para añadir 0 a números.

Expresiones regulares

Las expresiones regulares ( regular expressions, regex, pattern matching) son un lenguaje usado para parsear y manipular texto. Se usan comúnmente para hacer operaciones de búsqueda y reemplazo y para validar si un texto está bien formado.

Expresiones comunes
  • “a” = letra “a”
  • “^a” = empieza con la letra “a”
  • “a$” = finaliza con la letra “a”
  • ” = contiene cualquier letra (o número) de las contenidas en los corchetes
  • “[ - ]” = contiene cualquier letra (o número) dentro de un rango
  • “[^ae]” = cualquier cosa excepto determinadas letras (o números)
  • “{3}” = repite la expresión regular 3 veces

Las expresiones regulares son un mundo en si mismo, aquí tienes una pequeña chuleta : https://www.rstudio.com/wp-content/uploads/2016/09/RegExCheatsheet.pdf

rcosas = c("baseR", "R-Ladies", "Rmeetup", "Rmarkdown", "stringR")
str_detect(rcosas, pattern = "^R")
[1] FALSE  TRUE  TRUE  TRUE FALSE
rcosas[str_detect(rcosas, pattern = "^R")]
[1] "R-Ladies"  "Rmeetup"   "Rmarkdown"
rcosas[str_detect(rcosas, pattern = "R")]
[1] "baseR"     "R-Ladies"  "Rmeetup"   "Rmarkdown" "stringR"  
cleanR <- c("tidyverse", "tidyr","dplyr", "ggplot2", "tidytext", "purrr")
str_locate(cleanR, "tidy")
     start end
[1,]     1   4
[2,]     1   4
[3,]    NA  NA
[4,]    NA  NA
[5,]     1   4
[6,]    NA  NA
Otras funciones
  • str_extract(string, pattern) o str_extract_all(): busca la palabra exacta (normalmente se utiliza con expresiones regulares concatenadas)
  • str_match(string, pattern) o str_match_all(): es una función equivalente pero devuelve una matriz
str_match(c("12345678", "12587465", "dni desconocido"), pattern = "[1-9]{8}")
     [,1]      
[1,] "12345678"
[2,] "12587465"
[3,] NA        
str_match_all(c("12345678", "12587465", "dni desconocido"), pattern = "[1-9]{8}")
[[1]]
     [,1]      
[1,] "12345678"

[[2]]
     [,1]      
[1,] "12587465"

[[3]]
     [,1]
  • str_replace(string, pattern, replacement): reemplaza la primera instancia, str_replace_all para reemplazarlas todas
str_replace(c("castanya", "otonyo", "veronyo", "anyo", "nyonyo"), pattern = "ny", replacement = "ñ")
[1] "castaña" "otoño"   "veroño"  "año"     "ñonyo"  
str_replace_all(c("castanya", "otonyo", "veronyo", "anyo", "nyonyo"), pattern = "ny", replacement = "ñ")
[1] "castaña" "otoño"   "veroño"  "año"     "ñoño"   
  • str_split(string, pattern): separa una cadena en un vector, str_split_fixed(string, pattern, n) lo hace en un número n determinado de elementos
print(str_split("Eres muy chu chu chuli",pattern = " "))
[[1]]
[1] "Eres"  "muy"   "chu"   "chu"   "chuli"
print(length(str_split("Eres muy chu chu chuli",pattern = " ")[[1]]))
[1] 5

Analizando texto con R: los Simpsons fuente de sabiduría

Leer los subtitutos

La librería subtools permite leer archivos .str y .sub, así como organizar cada diálogo en un data frame. Los archivos han de estar organizados en directorios por temporadas y cada uno ha de estar nombrado como S01xE01 para que se parsee correctamente el número de temporada y de episodio.

Nos centraremos en las 9 primeras temporadas de los Simpsons. Si descargamos la puntuación y representamos gráficamente el promedio del score por temporada, se observa un claro descenso a partir de la 10. Por otro lado son las temporadas que mejor conocemos gracias a sus numerosas repeticiones :)

knitr::include_graphics("./images/ratings.png",dpi = 100)

A continuación leemos los subtítulos ( en inglés ) de las 9 primeras temporadas de los Simpsons.

library(subtools)
a <- read.subtitles.serie(dir = "./The Simpsons/")
Read: 9 seasons, 205 episodes
df <- subDataFrame(a)
df <- df[complete.cases(df), ]
str(df)
'data.frame':   58066 obs. of  8 variables:
 $ ID          : chr  "<U+FEFF>1""| __truncated__ "2" "3" "4" ...
 $ Timecode.in : chr  "00:00:00.042" "00:00:08.759" "00:00:10.761" "00:00:15.557" ...
 $ Timecode.out: chr  "00:00:00.042" "00:00:10.677" "00:00:13.180" "00:00:17.851" ...
 $ Text        : chr  "23.976" "Ooh! Careful, Homer!" "There's no time. We're late." "O little town of Bethlehem" ...
 $ season      : chr  "season 1" "season 1" "season 1" "season 1" ...
 $ season_num  : num  1 1 1 1 1 1 1 1 1 1 ...
 $ episode_num : num  1 1 1 1 1 1 1 1 1 1 ...
 $ serie       : chr  "The Simpsons" "The Simpsons" "The Simpsons" "The Simpsons" ...

Primer análisis: tm

Una primera opción es emplear la librería tm para nuestro análisis. La función tm_map nos va a permitir preparar nuestor documento para el análisis:

  • conformar un corpus (conjunto estructurado de textos / documentos )
  • transformar en minúsculas nuestro texto
  • eliminar los signos de puntuación
  • eliminar los números
  • eliminar las stopwords
  • eliminar los espacios en blanco
library(tm)
c <- tmCorpus(a)
c <- tm_map(c, content_transformer(tolower))
c <- tm_map(c, removePunctuation)
c <- tm_map(c, removeNumbers)
c <- tm_map(c, removeWords, stopwords("english"))
c <- tm_map(c, stripWhitespace)
c
<<SimpleCorpus>>
Metadata:  corpus specific: 1, document level (indexed): 4
Content:  documents: 205

El segundo paso, una vez preparado nuestro corpus, será el de construir la matriz de términos documentos. Para simplificar el análisis cada temporada constituirá un documento. De esta forma obtenemos para cada término la frecuencia por temporada.

TDM <- TermDocumentMatrix(c)
TDM <- as.matrix(TDM)
vec.season <- c(rep(x = 1,13), rep(2, 22), rep(3,24), rep(4,22), rep(5,24), rep(6,25), rep(7,25),rep(8,25), rep(9,25)) #episodios por temp
TDM.season <- t(apply(TDM, 1, function(x) tapply(x, vec.season, sum)))
colnames(TDM.season) <- paste0("S_", unique(vec.season))
head(TDM.season)
           
Terms       S_1 S_2 S_3 S_4 S_5 S_6 S_7 S_8 S_9
  able        6   4   3   0   6   7   4   2   4
  aboard      1   1   2   2   2   2   2   1   1
  acts        1   1   1   0   0   1   0   0   1
  adult       1   3   2   0   3   3   1   1   1
  affecting   1   0   0   0   0   0   0   0   0
  afford      5   8   5   1   2   4   8   6   1

A continuación representamos en una nube de términos dichas frecuencias: el tamaño indica el número de repeticiones y el color la temporada en la que más repeticiones presenta. La posición del término respecto de la etiqueta de temporada indica también la frecuencia por temporada.

library(wordcloud)
set.seed(100)
comparison.cloud(TDM.season, title.size = 1, max.words = 200, random.order = T)

Text mining con tidytext

La librería tidytext nos permite realizar operaciones como tf (frecuencia de términos) o tf_idf (frecuencia de término - frecuencia inversa de documento) con mayor agilidad.

Preparar el corpus

De nuevo vamos a preparar el nuestros diálogos para el análisis eliminado las stopwords.

library(tidytext)
library(tidyverse)
data(stop_words)
tidy_df <- df %>%
  unnest_tokens(word, Text) %>%
  dplyr::anti_join(stop_words)

Eliminamos además aquellas “palabras” constituidas exclusivamente por números y la palabra simpson.

library(data.table)
tidy_df <- as.data.table(tidy_df)
tidy_df <- tidy_df[is.na(as.numeric(word))]
tidy_df <- tidy_df[word != 'simpson']

Term frequency

Con nuestro data set limpio podemos representar en un gráfico de barras la frecuencia de términos. Lo que haremos será:

  • agrupar por temporada
  • sumar el número de veces que aparece el término (count)
  • tomar el top 10 por temporada
  • representar en un barplot para cada temporada dicha frecuencia
library(ggplot2)
tidy_df %>% group_by(season) %>%
        count(word, sort = FALSE) %>%
        top_n(15) %>%
        ggplot(aes(reorder(word,n), n, fill = season)) +
        geom_col() +
        coord_flip() +
        facet_wrap(~season, scales = "free_y") +
        labs(x = NULL) +
        guides(fill = FALSE) +
        scale_fill_brewer(palette = "Set1")
Selecting by n

Ahora que disponemos de la frecuencia podemos analizar la evolución en sus apariciones / tramas de cada personaje: + calculamos la frecuencia para todos los términos, no solo el top 15 + creamos dos listas, la familia Simpson y otros personajes relevantes de la trama + representamos ambas series series temporales

#install.packages('plotly')
require(plotly)
tidy_tf <- tidy_df %>% group_by(season) %>%
        count(word, sort = TRUE)
tidy_tf <- as.data.table(tidy_tf)
simpson_family <- c('homer', 'bart', 'lisa', 'maggie', 'marge', 'patty','selma')
other_characters <-  c('moe', 'ned', 'barney', 'modd', 'itchy', 'scratchy', 'krusty', 'burns', 'lenny', 'carl', 'edna', 'nelson', 'apu', 'milhouse', 'ralph', 'skinner', 'bob')
myplot <- ggplot(tidy_tf[tidy_tf$word %in% simpson_family], aes(x=season, y=n, group=word)) +
  geom_line(aes(color=word), size=1.25)+
  geom_point(aes(color=word))
ggplotly(myplot)
myplot <- ggplot(tidy_tf[tidy_tf$word %in% other_characters], aes(x=season, y=n, group=word)) +
  geom_line(aes(color=word), size=0.75)+
  geom_point(aes(color=word))
ggplotly(myplot)

Bigramas

También podemos obtener y representar los bigramas (conjunto de 2 términos) y sus frecuencias. Se representan en forma de grafo los más comunes (aquellos que aparecen al menos 7 veces en una misma temporada)

library(tidyr)
library(igraph)
library(tidytext)
bigram_graph <- df %>%
  unnest_tokens(bigram, Text, token = "ngrams", n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% stop_words$word) %>%
  filter(!word2 %in% stop_words$word) %>% 
  group_by(season) %>%
  count(word1, word2, sort = TRUE) %>%
  select(word1, word2, season, n) %>%
  filter(n > 7) %>%
  graph_from_data_frame()
# str(bigram_graph)
library(ggraph)
set.seed(2017)
ggraph(bigram_graph, layout = "fr") +
  geom_edge_link() +
  geom_node_point() +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1)

a <- grid::arrow(type = "closed", length = unit(.15, "inches"))
ggraph(bigram_graph, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "lightblue", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1) +
  theme_void()

También podemos realizar tf_idf sobre nuestro corpus para localizar las palabras más relevantes, en general y por temporada

library(dplyr)
tf_idf_df <- tidy_df %>% 
        count(season, word, sort = TRUE) %>%
        bind_tf_idf(word, season, n)
tf_idf_df <- tf_idf_df[order(-tf_idf_df$tf_idf),] 
tf_idf_df %>% 
  top_n(20) %>%
  ggplot(aes(word, tf_idf, fill = season)) +
  geom_col() +
  labs(x = NULL, y = "tf-idf") +
  coord_flip()
Selecting by tf_idf

tf_idf_df %>% 
  group_by(season) %>% 
  top_n(8) %>% 
  ungroup %>%
  ggplot(aes(reorder(word, tf_idf), tf_idf, fill = season)) +
  geom_col(show.legend = FALSE) +
  labs(x = NULL, y = "tf-idf") +
  facet_wrap(~season, ncol = 2, scales = "free") +
  coord_flip()
Selecting by tf_idf

Análisis de sentimiento

Valorar como positivo o negativo un mensaje es una tarea compleja, que requiere no sólo conocer el significado de las palabras sino también contextualizarlas, conocer la entonación en que se produce el mensaje, etc.

En este caso vamos a realizar una aproximación mucho más simple, que es la de considerar el texto como la combinación de palabras individuales y el sentimiento como la suma del sentimiento asociado a cada una de las palabras. Para ello tidytextnos ofrece tres posibles datasets (lexicon) de sentimientos: + AFINN from Finn Årup Nielsen (-5, 5), + bing from Bing Liu and collaborators(“positive” / “negative”) + nrc from Saif Mohammad and Peter Turney (“yes” / “no”).

Todos ellos basados en unigramas.

library(tidyr)
simpson_sentiment <- tidy_df %>%
  inner_join(get_sentiments("bing")) %>%
  count(season_episode, sentiment) %>%
  spread(sentiment, n, fill = 0) %>%
  mutate(sentiment = positive - negative)
Joining, by = "word"
head(simpson_sentiment,3)

afinn <- tidy_df %>% 
  inner_join(get_sentiments("afinn")) %>% 
  group_by(season_episode) %>% 
  summarise(sentiment = sum(score)) %>% 
  mutate(method = "AFINN")
Joining, by = "word"
afinn <- as.data.table(afinn)
afinn[, season:= str_sub(season_episode,start = 1, end = 2)]
afinn[, season:= str_replace(season, "S", "Season ")]
afinn[, episode:= str_sub(season_episode,start = 4, end = 6)]
ggplot(afinn, aes(episode, sentiment, fill = season)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~season, ncol = 2, scales = "free_x")

Anexos y otra información útil

LS0tDQp0aXRsZTogIlItTGFkaWVzICsgTGluZ3dhcnM6IFRhbGxlciBkZSBtaW5lcu1hIGRlIHRleHRvcyBjb24gUiINCnN1YnRpdGxlOiAiUi1MYWRpZXMgTWFkcmlkLCBWZXLzbmljYSBHYXJj7WEgeSBDbGF1ZGlhIEd1aXJhbywgMTcvMTAvMjAxNyINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6IA0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICBmaWdfd2lkdGg6IDgNCiAgICB0b2M6IHllcw0KLS0tDQojIyC/UXVp6W5lcyBzb21vcz8NCg0KLSAqKlZlcvNuaWNhIEdhcmPtYSoqLCBAeXJ5YWEgDQotICoqQ2xhdWRpYSBHdWlyYW8qKiwgQGNsYXVkaWFndWlyYW8gDQoNClJMYWRpZXMgeSBEYXRhIFNjaWVudGlzdHMgZW4gKipLZXJuZWwgQW5hbHl0aWNzKiogDQoNCkPzZGlnbyB5IGRhdG9zIGVuIGh0dHBzOi8vZ2l0aHViLmNvbS9pbnRpdmVkYS9ybGFkaWVzX3RleHRtaW5pbmcgDQoNCiMjIFRleHQgTWluaW5nICYgTkxQDQoNCkdyYW4gcGFydGUgZGUgbG9zIGRhdG9zIHNlIGVuY3VlbnRyYW4gbm8gZXN0cnVjdHVyYWRvcywgZXMgaW1wb3J0YW50ZSBjb25vY2VyIHTpY25pY2FzIHF1ZSBub3MgcGVybWl0YW4gb2J0ZW5lciBjb25jbHVzaW9uZXMgYSBwYXJ0aXIgZGUgbG9zIG1lbnNhamVzIHF1ZSBnZW5lcmFuIG51ZXN0cmFzIG9yZ2FuaXphY2lvbmVzLCBjbGllbnRlcyBvIHVzdWFyaW9zLiANCg0KSG95IGFwcmVuZGVyZW1vcyBhbGd1bmFzIHTpY25pY2FzIGLhc2ljYXMgcGFyYSBtYW5pcHVsYXIgY2FkZW5hcyBkZSB0ZXh0byB5IGFwbGljYXJlbW9zIHTpY25pY2FzIGRlIE5MUCBhIHN1YnTtdHVsb3MgcGFyYSBvYnRlbmVyIGFsZ3VuYXMgY29uY2x1c2lvbmVzLiANCg0KIyMgTWF0ZXJpYWxlcyB5IGxpYnJlcu1hcyANCg0KKiBTdWJ07XR1bG9zIHRlbXBvcmFkYXMgZGUgbG9zIFNpbXBzb25zDQoqIExpYnJlcu1hczoNCiArIGBgYHN0cmluZ3JgYGANCiArIGBgYHN1YnRvb2xzYGBgDQogKyBgYGB0bWBgYA0KICsgYGBgdGlkeXRleHRgYGANCiArIGBgYHRpZHl2ZXJzZWBgYCANCiArIGBgYGRwbHlyYGBgDQogKyBgYGBkYXRhdGFibGVgYGANCiArIGBgYGdncGxvdDJgYGANCiArIGBgYHBsb3RseWBgYChvcGNpb25hbCkNCiArIGBgYGlncmFwaGBgYA0KICsgYGBgZ2dyYXBoYGBgDQogKyBgYGB3b3JsZGNsb3VkYGBgDQoNClB1ZWRlcyBzZWd1aXIgZXN0ZSB0dXRvcmlhbCBkZSBkb3MgZm9ybWFzOg0KDQoxLiBFc2N1Y2hhbmRvIGFjdGl2YW1lbnRlLCBwYXJhIHByb2JhciBsdWVnbyBlbiBjYXNhIDopDQoyLiBEZSBmb3JtYSBhY3RpdmEsIGVqZWN1dGFuZG8gZW4gdHUgZXF1aXBvOg0KKiBEZXNjYXJnYSBlbCBub3RlYm9vayBkZWwgcmVwb3NpdG9yaW8uIA0KKiBBYnJlIGVsIGFyY2hpdm8gYGBgLlJtZGBgYCBlbiBSIFN0dWRpbyAocGFyYSBwb2RlciBlamVjdXRhciBub3RlYm9va3MgbmVjZXNpdGFy4XMgYWxndW5hcyBkZXBlbmRlbmNpYXMpDQoqIEZpamEgZWwgX193b3JraW5nIGRpcmVjdG9yeV9fIGRlbnRybyBkZSBsYSBjYXJwZXRhIGBgYHJsYWRpZXNfdGV4dG1pbmluZ2BgYDogYGBgc2V0d2QoImVsZGlyZWN0b3Jpb2RvbmRlaGFzZGVzY2FyZ2Fkb2VscmVwby9ybGFkaWVzX3RleHRtaW5pbmcvIilgYGANCiogQ2hlcXVlYSBxdWUgdGllbmVzIGxhcyBsaWJyZXLtYXMgaW5zdGFsYWRhcy4gTm8gdGUgYXB1cmVzIHNpIHdvcmxkY2xvdWQgbyBrbml0LCBhcGFyZWNlbiBjb21vIG5vIGluc3RhbGFkYXMsIGVzIHBvc2libGUgcXVlIG5vIGVzdOluIGRpc3BvbmlibGVzIHBhcmEgdHUgdmVyc2nzbiBSIA0KDQpgYGB7ciwgZWNobz1UUlVFLCB3YXJuaW5nPVRSVUV9DQpjKA0KICAic3RyaW5nciIsDQogICJzdWJ0b29scyIsDQogICJ0bSIsDQogICJ0aWR5dGV4dCIsDQogICJ0aWR5dmVyc2UiLA0KICAiZHBseXIiLA0KICAiZGF0YS50YWJsZSIsDQogICJnZ3Bsb3QyIiwNCiAgInBsb3RseSIsDQogICJpZ3JhcGgiLA0KICAiZ2dyYXBoIiwNCiAgIndvcmxkY2xvdWQiLA0KICAia25pdCINCiAgKSAlaW4lIHJvd25hbWVzKGluc3RhbGxlZC5wYWNrYWdlcygpKQ0KYGBgDQoNCiMjIEJhY2syYmFzaWNzOiBTdHJpbmdzIHkgZXhwcmVzaW9uZXMgcmVndWxhcmVzDQoNCkxhcyBjYWRlbmFzIG8gc3RyaW5ncyBjdW1wbGVuIHVuIHBhcGVsIGltcG9ydGFudGUgZW4gbGFzIHRhcmVhcyBkZSBFVEwgbyBwcmVwYXJhY2nzbiBkZSBsb3MgZGF0b3MuIFVuYSBkZSBsYXMgbGlicmVy7WFzIGVzZW5jaWFsZXMgZW4gbGEgbWF0ZXJpYSBlcyAqKnN0cmluZyoqDQoNCmBgYFN0cmluZ3JgYGAgZXMgdW5vIGRlIGxvcyBwYXF1ZXRlcyBkaXNl8WFkb3MgcG9yIEhhZGxleSBXaWNraGFtIHBhcmEgYXNpc3RpciBlbiBsYXMgdGFyZWFzIGRlIG1hbmlwdWxhY2nzbiBkZSBzdHJpbmdzOg0KDQorIHNlIGludGVncmEgY29uIF9waXBlc18gKCU8JSkNCisgc3VzIGZ1bmNpb25lcyBzb24gY29uc2lzdGVudGVzIHkgZuFjaWxlcyBkZSBpbnRlcnByZXRhcg0KDQojIyMgv1F16SBzb24gbGFzIGNhZGVuYXMgZGUgdGV4dG8gbyBfc3RyaW5nc18/DQoNCisgTGFzIGNhZGVuYXMgZGUgdGV4dG8gc2UgZW5jdWVudHJhbiBjb250ZW5pZGFzIGVudHJlIGNvbWlsbGFzIGAiImAgKCp1c2FyIGxhIGNvbWlsbGEgc2ltcGxlIGAnYCBwYXJhIGVzY2FwYXIgbGEgZG9ibGUgY29taWxsYSkNCisgUHVlZGVuIGNvbnRlbmVyIGxldHJhcyBgImEiYCwgbvptZXJvcyBgIjEiYCwgc2ltYm9sb3MgYCImImAgbyB0b2RvIGxvIGFudGVyaW9yIGAiMWEmImANCisgTWllbnRyYXMgcXVlIGxvcyBu+m1lcm9zIHB1ZWRlIHNlciBhIGxhIHZleiBfaW50ZWdlcnNfIHkgX2NoYXJhY3RlcnNfLCBsYXMgbGV0cmFzIHkgbG9zIHPtbWJvbG9zIG5vIHRpZW5lbiBzaWduaWZpY2FkbyBjb21vIGludGVnZXIgeSBzZSB0cmFkdWNlbiBlbiBgTkFgDQoNCmBgYHtyIGFzLmludGVnZXIsIHdhcm5pbmc9RkFMU0V9DQphcy5pbnRlZ2VyKGMoImEiLCAiJiIsICIxMjMiKSkNCmBgYA0KDQpgYGB7ciBsaXN0MX0NCmMoZmFjdG9yKCJhIiksICJiIiwgIiYiLDEpDQpgYGANCkNvbmNhdGVuYXIgaW50ZWdlcnMgeSBjaGFyYWN0ZXJzLCBjb252aWVydGUgYXV0b23hdGljYW1lbnRlIGxvcyBpbnRlZ2VycyBlbiBfY2hhcmFjdGVyc18uIA0KDQpgYGB7ciBsaXN0Mn0NCmMoYXMuY2hhcmFjdGVyKGZhY3RvcigiYSIpKSwgImIiLCAiJiIsMSkNCmBgYA0KDQojIyMgT3BlcmFjaW9uZXMgYuFzaWNhcw0KDQojIyMjIEltcG9ydGFyIGxhIGxpYnJlcu1hDQpgYGB7ciBpbXBvcnRzLCB3YXJuaW5nPUZBTFNFfQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJzdHJpbmdyIikNCmxpYnJhcnkoc3RyaW5ncikNCmBgYA0KIyMjIyBPcGVyYWRvcmVzDQoNCk11Y2hhcyBkZSBlc3RhcyBmdW5jaW9uZXMgdGllbm5lIHN1IGVxdWl2YWxlbnRlIGVuIFIgYmFzZSwgcHVlZGVuIHNlciBt4XMgbGVudGFzL21lbm9zIGVmaWNpZW50ZXMNCg0KKyBgc3RyX3RvX3VwcGVyKHN0cmluZylgOiBjb252aWVydGUgdW4gc3RyaW5nIGVuIG1hefpzY3VsYXMNCisgYHN0cl90b19sb3dlcihzdHJpbmcpYDogY29udmllcnRlIHVuIHN0cmluZyBlbiBtaW76c2N1bGFzDQorIGBzdHJfdG9fdGl0bGUoc3RyaW5nKWA6IGNhcGl0YWxpemEgdW4gc3RyaW5nDQoNCg0KYGBge3IgdGVtYXN9DQp0ZW1hcyA8LSBjKCJD82RpZ28iLCAiTXVqZXJlcyIsICJ0ZWNub2xvZ+1hIiwgIkluZm9ybeF0aWNhIiwgImVzdGFk7XN0aWNhIiwgIldvbWVuIiwgIkNvZGVycyIsICJBcHJlbmRpemFqZSIsICJhdXRvbeF0aWNvIiwgIkFu4Wxpc2lzIiwgImRhdG9zIiwgIlZpc3VhbGl6YWNp824iLCAiUi1MYWRpZXMiLCAiU29jaWFsIiwgIkNvZGluZyIsICJSIiwgIkNpZW5jaWEiLCAiUHJvZ3JhbW1pbmciKQ0KYGBgDQoNCmBgYHtyIG1heXVzfQ0Kc3RyX3RvX3VwcGVyKHRlbWFzKQ0KYGBgDQpgYGB7ciBtaW51c30NCnN0cl90b19sb3dlcih0ZW1hcykNCmBgYA0KYGBge3IgY2FwaX0NCnN0cl90b190aXRsZSh0ZW1hcykNCmBgYA0KDQorIGBzdHJfYyhzdHJpbmcsIHNlcCA9ICIiKWA6IGp1bnRhIHZhcmlvcyBzdHJpbmcgZW4gdW5vIHNvbG8sIGVzIGVsIGVxdWl2YWxlbnRlIGEgYHBhc3RlKHNlcCA9ICIiKWAgbyBgcGFzdGUwKClgDQorIGBzdHJfbGVuZ3RoKHN0cmluZylgOiBkZXZ1ZWx2ZSBsYSBsb25naXR1ZCBkZWwgc3RyaW5nLCBlcyBzaW1pbGFyIGEgbGEgZnVuY2nzbiBgbmNoYXIoKWAuIENvbnZpZXJ0ZSBsb3MgZmFjdG9yZXMgZW4gc3RyaW5ncyB5IGNvbnNlcnZhIGxvcyBOQSdzDQoNCmBgYHtyIGxlbmd0aH0NCnByaW50KHN0cl9sZW5ndGgoJ1ItTGFkaWVzJykpDQpwcmludChzdHJfbGVuZ3RoKE5BKSkNCmBgYA0KDQorIGBzdHJfc3ViKHN0cmluZywgc3RhcnQsIGVuZClgOiBzdWJzZXRlYSB1biBzdHJpbmcgbyB1biB2ZWN0b3IgZGUgc3RyaW5nIGVzcGVjaWZpY2FuZG8gbGEgcG9zaWNp824gaW5pY2lhbCB5IGxhIGZpbmFsLCBlcyBlbCBlcXVpdmFsZW50ZSBlbiBSIGJhc2UgYSBzdWJzdHIoKS4gUG9yIGRlZmVjdG8gZmluYWxpemEgZW4gZWwg+mx0aW1vIGNhcmFjdGVyLg0KDQpgYGB7ciBzdWJzZXR0aW5nMn0NCnByaW50KHRlbWFzWzE6NF0pDQpzdHJfc3ViKHN0cmluZyA9IHRlbWFzWzE6NF0sIHN0YXJ0PTMpDQpgYGANCisgYHN0cl9kdXAoc3RyaW5nLCB0aW1lcylgOiBjb3BpYSB5IHBlZ2EgdW4gc3RyaW5nIHVuIG76bWVybyBkZXRlcm1pbmFkbyBkZSB2ZWNlcw0KYGBge3IgZHVwbGljYXRpbmd9DQpzdHJfZHVwKHN0cmluZyA9IHRlbWFzWzE6NF0sIHRpbWVzID0gMykNCmBgYA0KDQorIGBzdHJfdHJpbShzdHJpbmcsIHNpZGUgPSBjKCJib3RoIiwgImxlZnQiLCAicmlndGgiKSlgOiBlbGltaW5hIGxvcyBlc3BhY2lvcyB2YWPtb3MsIHBvciBkZWZlY3RvIHRvbWEgZWwgdmFsb3IgX2JvdGhfLiBNZWpvciBldml0YXIgYGdzdWIoIiAiLCAiIiwgc3RyaW5nKWAgDQoNCisgYHN0cl9wYWQoc3RyaW5nLCB3aWR0aCwgc2lkZSA9IGMoImxlZnQiLCAiYm90aCIsICJyaWdodCIpLCBwYWQgPSAiICIpKWA6IGHxYWRlIGEgc3RyaW5ncyBlc3BhY2lvcyBlbiBibGFuY28gcGFyYSBpZ3VhbGFybG9zIGVuIGxvbmdpdHVkLCBlc3BlY2lhbG1lbnRlIPp0aWwgcGFyYSBh8WFkaXIgMCBhIG76bWVyb3MuIA0KDQojIyMgRXhwcmVzaW9uZXMgcmVndWxhcmVzDQoNCkxhcyBleHByZXNpb25lcyByZWd1bGFyZXMgKCBfcmVndWxhciBleHByZXNzaW9uc18sIF9yZWdleF8sIF9wYXR0ZXJuIG1hdGNoaW5nXykgc29uIHVuIGxlbmd1YWplIHVzYWRvIHBhcmEgcGFyc2VhciB5IG1hbmlwdWxhciB0ZXh0by4gU2UgdXNhbiBjb236bm1lbnRlIHBhcmEgaGFjZXIgb3BlcmFjaW9uZXMgZGUgYvpzcXVlZGEgeSByZWVtcGxhem8geSBwYXJhIHZhbGlkYXIgc2kgdW4gdGV4dG8gZXN04SBiaWVuIGZvcm1hZG8uIA0KDQojIyMjIyBFeHByZXNpb25lcyBjb211bmVzDQoNCi0gImEiICA9IGxldHJhICJhIg0KLSAiXmEiID0gZW1waWV6YSBjb24gbGEgbGV0cmEgImEiDQotICJhJCIgPSBmaW5hbGl6YSBjb24gbGEgbGV0cmEgImEiDQotICJbIF0iID0gY29udGllbmUgY3VhbHF1aWVyIGxldHJhIChvIG76bWVybykgZGUgbGFzIGNvbnRlbmlkYXMgZW4gbG9zIGNvcmNoZXRlcw0KLSAiWyAtIF0iID0gY29udGllbmUgY3VhbHF1aWVyIGxldHJhIChvIG76bWVybykgZGVudHJvIGRlIHVuIHJhbmdvDQotICJbXmFlXSIgPSBjdWFscXVpZXIgY29zYSBleGNlcHRvIGRldGVybWluYWRhcyBsZXRyYXMgKG8gbvptZXJvcykNCi0gInszfSIgPSByZXBpdGUgbGEgZXhwcmVzafNuIHJlZ3VsYXIgMyB2ZWNlcw0KDQpMYXMgZXhwcmVzaW9uZXMgcmVndWxhcmVzIHNvbiB1biBtdW5kbyBlbiBzaSBtaXNtbywgYXF17SB0aWVuZXMgdW5hIHBlcXVl8WEgX2NodWxldGFfIDogaHR0cHM6Ly93d3cucnN0dWRpby5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMTYvMDkvUmVnRXhDaGVhdHNoZWV0LnBkZiANCg0KDQpgYGB7ciByZWdleH0NCnJjb3NhcyA9IGMoImJhc2VSIiwgIlItTGFkaWVzIiwgIlJtZWV0dXAiLCAiUm1hcmtkb3duIiwgInN0cmluZ1IiKQ0Kc3RyX2RldGVjdChyY29zYXMsIHBhdHRlcm4gPSAiXlIiKQ0KYGBgDQpgYGB7ciByZWdleDJ9DQpyY29zYXNbc3RyX2RldGVjdChyY29zYXMsIHBhdHRlcm4gPSAiXlIiKV0NCmBgYA0KDQpgYGB7ciByZWdleDN9DQpyY29zYXNbc3RyX2RldGVjdChyY29zYXMsIHBhdHRlcm4gPSAiUiIpXQ0KYGBgDQpgYGB7ciByZWdleDR9DQpjbGVhblIgPC0gYygidGlkeXZlcnNlIiwgInRpZHlyIiwiZHBseXIiLCAiZ2dwbG90MiIsICJ0aWR5dGV4dCIsICJwdXJyciIpDQpzdHJfbG9jYXRlKGNsZWFuUiwgInRpZHkiKQ0KYGBgDQoNCiMjIyMjIE90cmFzIGZ1bmNpb25lcyANCisgYHN0cl9leHRyYWN0KHN0cmluZywgcGF0dGVybilgIG8gYHN0cl9leHRyYWN0X2FsbCgpYDogYnVzY2EgbGEgcGFsYWJyYSBleGFjdGEgKG5vcm1hbG1lbnRlIHNlIHV0aWxpemEgY29uIGV4cHJlc2lvbmVzIHJlZ3VsYXJlcyBjb25jYXRlbmFkYXMpDQorIGBzdHJfbWF0Y2goc3RyaW5nLCBwYXR0ZXJuKWAgbyBgc3RyX21hdGNoX2FsbCgpYDogZXMgdW5hIGZ1bmNp824gZXF1aXZhbGVudGUgcGVybyBkZXZ1ZWx2ZSB1bmEgbWF0cml6DQoNCmBgYHtyIHJlZ2V4NX0NCnN0cl9tYXRjaChjKCIxMjM0NTY3OCIsICIxMjU4NzQ2NSIsICJkbmkgZGVzY29ub2NpZG8iKSwgcGF0dGVybiA9ICJbMS05XXs4fSIpDQpgYGANCmBgYHtyIHJlZ2V4Nn0NCnN0cl9tYXRjaF9hbGwoYygiMTIzNDU2NzgiLCAiMTI1ODc0NjUiLCAiZG5pIGRlc2Nvbm9jaWRvIiksIHBhdHRlcm4gPSAiWzEtOV17OH0iKQ0KYGBgDQoNCg0KKyBgc3RyX3JlcGxhY2Uoc3RyaW5nLCBwYXR0ZXJuLCByZXBsYWNlbWVudClgOiByZWVtcGxhemEgbGEgcHJpbWVyYSBpbnN0YW5jaWEsIGBzdHJfcmVwbGFjZV9hbGxgIHBhcmEgcmVlbXBsYXphcmxhcyB0b2Rhcw0KYGBge3IgcmVnZXg3fQ0Kc3RyX3JlcGxhY2UoYygiY2FzdGFueWEiLCAib3RvbnlvIiwgInZlcm9ueW8iLCAiYW55byIsICJueW9ueW8iKSwgcGF0dGVybiA9ICJueSIsIHJlcGxhY2VtZW50ID0gIvEiKQ0KYGBgDQpgYGB7ciByZWdleDh9DQpzdHJfcmVwbGFjZV9hbGwoYygiY2FzdGFueWEiLCAib3RvbnlvIiwgInZlcm9ueW8iLCAiYW55byIsICJueW9ueW8iKSwgcGF0dGVybiA9ICJueSIsIHJlcGxhY2VtZW50ID0gIvEiKQ0KYGBgDQorIGBzdHJfc3BsaXQoc3RyaW5nLCBwYXR0ZXJuKWA6IHNlcGFyYSB1bmEgY2FkZW5hIGVuIHVuIHZlY3RvciwgYHN0cl9zcGxpdF9maXhlZChzdHJpbmcsIHBhdHRlcm4sIG4pYCBsbyBoYWNlIGVuIHVuIG76bWVybyBgbmAgZGV0ZXJtaW5hZG8gZGUgZWxlbWVudG9zDQoNCmBgYHtyIHJlZ2V4OX0NCnByaW50KHN0cl9zcGxpdCgiRXJlcyBtdXkgY2h1IGNodSBjaHVsaSIscGF0dGVybiA9ICIgIikpDQpwcmludChsZW5ndGgoc3RyX3NwbGl0KCJFcmVzIG11eSBjaHUgY2h1IGNodWxpIixwYXR0ZXJuID0gIiAiKVtbMV1dKSkNCmBgYA0KDQojIyBBbmFsaXphbmRvIHRleHRvIGNvbiBSOiBsb3MgU2ltcHNvbnMgZnVlbnRlIGRlIHNhYmlkdXLtYQ0KDQojIyMgTGVlciBsb3Mgc3VidGl0dXRvcw0KDQpMYSBsaWJyZXLtYSBgc3VidG9vbHNgIHBlcm1pdGUgbGVlciBhcmNoaXZvcyBgLnN0cmAgeSAgYC5zdWJgLCBhc+0gY29tbyBvcmdhbml6YXIgY2FkYSBkaeFsb2dvIGVuIHVuIGRhdGEgZnJhbWUuIA0KTG9zIGFyY2hpdm9zIGhhbiBkZSBlc3RhciBvcmdhbml6YWRvcyBlbiBkaXJlY3RvcmlvcyBwb3IgdGVtcG9yYWRhcyB5IGNhZGEgdW5vIGhhIGRlIGVzdGFyIG5vbWJyYWRvIGNvbW8gYFMwMXhFMDFgIHBhcmEgcXVlIHNlIHBhcnNlZSBjb3JyZWN0YW1lbnRlIGVsIG76bWVybyBkZSB0ZW1wb3JhZGEgeSBkZSBlcGlzb2Rpby4NCg0KTm9zIGNlbnRyYXJlbW9zIGVuIGxhcyA5IHByaW1lcmFzIHRlbXBvcmFkYXMgZGUgbG9zIFNpbXBzb25zLiBTaSBkZXNjYXJnYW1vcyBsYSBwdW50dWFjafNuIHkgcmVwcmVzZW50YW1vcyBncuFmaWNhbWVudGUgZWwgcHJvbWVkaW8gZGVsIHNjb3JlIHBvciB0ZW1wb3JhZGEsIHNlIG9ic2VydmEgdW4gY2xhcm8gZGVzY2Vuc28gYSBwYXJ0aXIgZGUgbGEgMTAuIA0KUG9yIG90cm8gbGFkbyBzb24gbGFzIHRlbXBvcmFkYXMgcXVlIG1lam9yIGNvbm9jZW1vcyBncmFjaWFzIGEgc3VzIG51bWVyb3NhcyByZXBldGljaW9uZXMgOikNCg0KDQpgYGB7ciwgb3V0LndpZHRoID0gIjE1MHB4In0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCIuL2ltYWdlcy9yYXRpbmdzLnBuZyIsZHBpID0gMTAwKQ0KDQpgYGANCg0KDQoNCkEgIGNvbnRpbnVhY2nzbiBsZWVtb3MgbG9zIHN1YnTtdHVsb3MgKCBfZW4gaW5nbOlzXyApIGRlIGxhcyA5IHByaW1lcmFzIHRlbXBvcmFkYXMgZGUgbG9zIFNpbXBzb25zLiANCg0KDQpgYGB7ciBzdWJ0b29scywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcGFnZWQucHJpbnQ9RkFMU0V9DQpsaWJyYXJ5KHN1YnRvb2xzKQ0KYSA8LSByZWFkLnN1YnRpdGxlcy5zZXJpZShkaXIgPSAiLi9UaGUgU2ltcHNvbnMvIikNCmRmIDwtIHN1YkRhdGFGcmFtZShhKQ0KZGYgPC0gZGZbY29tcGxldGUuY2FzZXMoZGYpLCBdDQpzdHIoZGYpDQpgYGANCg0KIyMjIFByaW1lciBhbuFsaXNpczogdG0NCg0KVW5hIHByaW1lcmEgb3BjafNuIGVzIGVtcGxlYXIgbGEgbGlicmVy7WEgYHRtYCBwYXJhIG51ZXN0cm8gYW7hbGlzaXMuIExhIGZ1bmNp824gYHRtX21hcGAgbm9zIHZhIGEgcGVybWl0aXIgcHJlcGFyYXIgbnVlc3RvciBkb2N1bWVudG8gcGFyYSBlbCBhbuFsaXNpczoNCg0KKyBjb25mb3JtYXIgdW4gY29ycHVzIChjb25qdW50byBlc3RydWN0dXJhZG8gZGUgdGV4dG9zIC8gZG9jdW1lbnRvcyApDQorIHRyYW5zZm9ybWFyIGVuIG1pbvpzY3VsYXMgbnVlc3RybyB0ZXh0bw0KKyBlbGltaW5hciBsb3Mgc2lnbm9zIGRlIHB1bnR1YWNp824NCisgZWxpbWluYXIgbG9zIG76bWVyb3MNCisgZWxpbWluYXIgbGFzIF9zdG9wd29yZHNfIA0KKyBlbGltaW5hciBsb3MgZXNwYWNpb3MgZW4gYmxhbmNvDQoNCmBgYHtyIGNsZWFudG0sIGVjaG89VFJVRSwgbWVzc2FnZT1UUlVFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0bSkNCmMgPC0gdG1Db3JwdXMoYSkNCmMgPC0gdG1fbWFwKGMsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpDQpjIDwtIHRtX21hcChjLCByZW1vdmVQdW5jdHVhdGlvbikNCmMgPC0gdG1fbWFwKGMsIHJlbW92ZU51bWJlcnMpDQpjIDwtIHRtX21hcChjLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJlbmdsaXNoIikpDQpjIDwtIHRtX21hcChjLCBzdHJpcFdoaXRlc3BhY2UpDQpjDQpgYGANCg0KRWwgc2VndW5kbyBwYXNvLCB1bmEgdmV6IHByZXBhcmFkbyBudWVzdHJvIGNvcnB1cywgc2Vy4SBlbCBkZSBjb25zdHJ1aXIgbGEgbWF0cml6IGRlIHTpcm1pbm9zIGRvY3VtZW50b3MuIFBhcmEgc2ltcGxpZmljYXIgZWwgYW7hbGlzaXMgY2FkYSB0ZW1wb3JhZGEgY29uc3RpdHVpcuEgdW4gZG9jdW1lbnRvLiANCkRlIGVzdGEgZm9ybWEgb2J0ZW5lbW9zIHBhcmEgY2FkYSB06XJtaW5vIGxhIGZyZWN1ZW5jaWEgcG9yIHRlbXBvcmFkYS4gDQoNCmBgYHtyLCAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpURE0gPC0gVGVybURvY3VtZW50TWF0cml4KGMpDQpURE0gPC0gYXMubWF0cml4KFRETSkNCnZlYy5zZWFzb24gPC0gYyhyZXAoeCA9IDEsMTMpLCByZXAoMiwgMjIpLCByZXAoMywyNCksIHJlcCg0LDIyKSwgcmVwKDUsMjQpLCByZXAoNiwyNSksIHJlcCg3LDI1KSxyZXAoOCwyNSksIHJlcCg5LDI1KSkgI2VwaXNvZGlvcyBwb3IgdGVtcA0KVERNLnNlYXNvbiA8LSB0KGFwcGx5KFRETSwgMSwgZnVuY3Rpb24oeCkgdGFwcGx5KHgsIHZlYy5zZWFzb24sIHN1bSkpKQ0KY29sbmFtZXMoVERNLnNlYXNvbikgPC0gcGFzdGUwKCJTXyIsIHVuaXF1ZSh2ZWMuc2Vhc29uKSkNCmhlYWQoVERNLnNlYXNvbikNCmBgYA0KDQpBIGNvbnRpbnVhY2nzbiByZXByZXNlbnRhbW9zIGVuIHVuYSBudWJlIGRlIHTpcm1pbm9zIGRpY2hhcyBmcmVjdWVuY2lhczogZWwgdGFtYfFvIGluZGljYSBlbCBu+m1lcm8gZGUgcmVwZXRpY2lvbmVzIHkgZWwgY29sb3IgbGEgdGVtcG9yYWRhIGVuIGxhIHF1ZSBt4XMgcmVwZXRpY2lvbmVzIHByZXNlbnRhLiBMYSBwb3NpY2nzbiBkZWwgdOlybWlubyByZXNwZWN0byBkZSBsYSBldGlxdWV0YSBkZSB0ZW1wb3JhZGEgaW5kaWNhIHRhbWJp6W4gbGEgZnJlY3VlbmNpYSBwb3IgdGVtcG9yYWRhLiANCg0KYGBge3IsICwgbWVzc2FnZT1UUlVFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh3b3JkY2xvdWQpDQpzZXQuc2VlZCgxMDApDQpjb21wYXJpc29uLmNsb3VkKFRETS5zZWFzb24sIHRpdGxlLnNpemUgPSAxLCBtYXgud29yZHMgPSAyMDAsIHJhbmRvbS5vcmRlciA9IFQpDQpgYGANCg0KIyMjIFRleHQgbWluaW5nIGNvbiB0aWR5dGV4dA0KDQpMYSBsaWJyZXLtYSB0aWR5dGV4dCBub3MgcGVybWl0ZSByZWFsaXphciBvcGVyYWNpb25lcyBjb21vIHRmIChmcmVjdWVuY2lhIGRlIHTpcm1pbm9zKSBvIHRmX2lkZiAoZnJlY3VlbmNpYSBkZSB06XJtaW5vIC0gZnJlY3VlbmNpYSBpbnZlcnNhIGRlIGRvY3VtZW50bykgY29uIG1heW9yIGFnaWxpZGFkLiANCg0KIyMjIyBQcmVwYXJhciBlbCBjb3JwdXMNCg0KRGUgbnVldm8gdmFtb3MgYSBwcmVwYXJhciBlbCBudWVzdHJvcyBkaeFsb2dvcyBwYXJhIGVsIGFu4Wxpc2lzIGVsaW1pbmFkbyBsYXMgc3RvcHdvcmRzLg0KDQpgYGB7ciBlbGltaW5hciBzdG9wd29yZHMsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1UUlVFfQ0KbGlicmFyeSh0aWR5dGV4dCkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQpkYXRhKHN0b3Bfd29yZHMpDQoNCnRpZHlfZGYgPC0gZGYgJT4lDQogIHVubmVzdF90b2tlbnMod29yZCwgVGV4dCkgJT4lDQogIGRwbHlyOjphbnRpX2pvaW4oc3RvcF93b3JkcykNCmBgYA0KDQpFbGltaW5hbW9zIGFkZW3hcyBhcXVlbGxhcyAicGFsYWJyYXMiIGNvbnN0aXR1aWRhcyBleGNsdXNpdmFtZW50ZSBwb3IgbvptZXJvcyB5IGxhIHBhbGFicmEgc2ltcHNvbi4NCg0KYGBge3IgIGNsZWFuaW5nLCB3YXJuaW5nPUZBTFNFLGVjaG89VFJVRX0NCmxpYnJhcnkoZGF0YS50YWJsZSkNCnRpZHlfZGYgPC0gYXMuZGF0YS50YWJsZSh0aWR5X2RmKQ0KdGlkeV9kZiA8LSB0aWR5X2RmW2lzLm5hKGFzLm51bWVyaWMod29yZCkpXQ0KdGlkeV9kZiA8LSB0aWR5X2RmW3dvcmQgIT0gJ3NpbXBzb24nXQ0KYGBgDQoNCiMjIyMgVGVybSBmcmVxdWVuY3kNCg0KQ29uIG51ZXN0cm8gZGF0YSBzZXQgbGltcGlvIHBvZGVtb3MgcmVwcmVzZW50YXIgZW4gdW4gZ3LhZmljbyBkZSBiYXJyYXMgbGEgZnJlY3VlbmNpYSBkZSB06XJtaW5vcy4gTG8gcXVlIGhhcmVtb3Mgc2Vy4ToNCg0KKyBhZ3J1cGFyIHBvciB0ZW1wb3JhZGENCisgc3VtYXIgZWwgbvptZXJvIGRlIHZlY2VzIHF1ZSBhcGFyZWNlIGVsIHTpcm1pbm8gKGBjb3VudGApDQorIHRvbWFyIGVsIHRvcCAxMCBwb3IgdGVtcG9yYWRhDQorIHJlcHJlc2VudGFyIGVuIHVuIF9iYXJwbG90XyBwYXJhIGNhZGEgdGVtcG9yYWRhIGRpY2hhIGZyZWN1ZW5jaWENCg0KDQpgYGB7ciBwbG90LCBmaWcud2lkdGg9MTAsIG1lc3NhZ2U9VFJVRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoZ2dwbG90MikNCnRpZHlfZGYgJT4lIGdyb3VwX2J5KHNlYXNvbikgJT4lDQogICAgICAgIGNvdW50KHdvcmQsIHNvcnQgPSBGQUxTRSkgJT4lDQogICAgICAgIHRvcF9uKDE1KSAlPiUNCiAgICAgICAgZ2dwbG90KGFlcyhyZW9yZGVyKHdvcmQsbiksIG4sIGZpbGwgPSBzZWFzb24pKSArDQogICAgICAgIGdlb21fY29sKCkgKw0KICAgICAgICBjb29yZF9mbGlwKCkgKw0KICAgICAgICBmYWNldF93cmFwKH5zZWFzb24sIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogICAgICAgIGxhYnMoeCA9IE5VTEwpICsNCiAgICAgICAgZ3VpZGVzKGZpbGwgPSBGQUxTRSkgKw0KICAgICAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDEiKQ0KYGBgDQoNCkFob3JhIHF1ZSBkaXNwb25lbW9zIGRlIGxhIGZyZWN1ZW5jaWEgcG9kZW1vcyBhbmFsaXphciBsYSBldm9sdWNp824gZW4gc3VzIGFwYXJpY2lvbmVzIC8gdHJhbWFzIGRlIGNhZGEgcGVyc29uYWplOg0KKyBjYWxjdWxhbW9zIGxhIGZyZWN1ZW5jaWEgcGFyYSB0b2RvcyBsb3MgdOlybWlub3MsIG5vIHNvbG8gZWwgdG9wIDE1DQorIGNyZWFtb3MgZG9zIGxpc3RhcywgbGEgZmFtaWxpYSBTaW1wc29uIHkgb3Ryb3MgcGVyc29uYWplcyByZWxldmFudGVzIGRlIGxhIHRyYW1hDQorIHJlcHJlc2VudGFtb3MgYW1iYXMgc2VyaWVzIHNlcmllcyB0ZW1wb3JhbGVzIA0KDQpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KI2luc3RhbGwucGFja2FnZXMoJ3Bsb3RseScpDQpyZXF1aXJlKHBsb3RseSkNCnRpZHlfdGYgPC0gdGlkeV9kZiAlPiUgZ3JvdXBfYnkoc2Vhc29uKSAlPiUNCiAgICAgICAgY291bnQod29yZCwgc29ydCA9IFRSVUUpDQp0aWR5X3RmIDwtIGFzLmRhdGEudGFibGUodGlkeV90ZikNCg0Kc2ltcHNvbl9mYW1pbHkgPC0gYygnaG9tZXInLCAnYmFydCcsICdsaXNhJywgJ21hZ2dpZScsICdtYXJnZScsICdwYXR0eScsJ3NlbG1hJykNCm90aGVyX2NoYXJhY3RlcnMgPC0gIGMoJ21vZScsICduZWQnLCAnYmFybmV5JywgJ21vZGQnLCAnaXRjaHknLCAnc2NyYXRjaHknLCAna3J1c3R5JywgJ2J1cm5zJywgJ2xlbm55JywgJ2NhcmwnLCAnZWRuYScsICduZWxzb24nLCAnYXB1JywgJ21pbGhvdXNlJywgJ3JhbHBoJywgJ3NraW5uZXInLCAnYm9iJykNCg0KbXlwbG90IDwtIGdncGxvdCh0aWR5X3RmW3RpZHlfdGYkd29yZCAlaW4lIHNpbXBzb25fZmFtaWx5XSwgYWVzKHg9c2Vhc29uLCB5PW4sIGdyb3VwPXdvcmQpKSArDQogIGdlb21fbGluZShhZXMoY29sb3I9d29yZCksIHNpemU9MS4yNSkrDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yPXdvcmQpKQ0KDQpnZ3Bsb3RseShteXBsb3QpDQoNCmBgYA0KDQpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbXlwbG90IDwtIGdncGxvdCh0aWR5X3RmW3RpZHlfdGYkd29yZCAlaW4lIG90aGVyX2NoYXJhY3RlcnNdLCBhZXMoeD1zZWFzb24sIHk9biwgZ3JvdXA9d29yZCkpICsNCiAgZ2VvbV9saW5lKGFlcyhjb2xvcj13b3JkKSwgc2l6ZT0wLjc1KSsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3I9d29yZCkpDQpnZ3Bsb3RseShteXBsb3QpDQpgYGANCg0KIyMjIyBCaWdyYW1hcw0KDQpUYW1iaeluIHBvZGVtb3Mgb2J0ZW5lciB5IHJlcHJlc2VudGFyIGxvcyBiaWdyYW1hcyAoY29uanVudG8gZGUgMiB06XJtaW5vcykgeSBzdXMgZnJlY3VlbmNpYXMuIFNlIHJlcHJlc2VudGFuIGVuIGZvcm1hIGRlIGdyYWZvIGxvcyBt4XMgY29tdW5lcyAoYXF1ZWxsb3MgcXVlIGFwYXJlY2VuIGFsIG1lbm9zIDcgdmVjZXMgZW4gdW5hIG1pc21hIHRlbXBvcmFkYSkNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShpZ3JhcGgpDQpsaWJyYXJ5KHRpZHl0ZXh0KQ0KDQpiaWdyYW1fZ3JhcGggPC0gZGYgJT4lDQogIHVubmVzdF90b2tlbnMoYmlncmFtLCBUZXh0LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lDQogIHNlcGFyYXRlKGJpZ3JhbSwgYygid29yZDEiLCAid29yZDIiKSwgc2VwID0gIiAiKSAlPiUNCiAgZmlsdGVyKCF3b3JkMSAlaW4lIHN0b3Bfd29yZHMkd29yZCkgJT4lDQogIGZpbHRlcighd29yZDIgJWluJSBzdG9wX3dvcmRzJHdvcmQpICU+JSANCiAgZ3JvdXBfYnkoc2Vhc29uKSAlPiUNCiAgY291bnQod29yZDEsIHdvcmQyLCBzb3J0ID0gVFJVRSkgJT4lDQogIHNlbGVjdCh3b3JkMSwgd29yZDIsIHNlYXNvbiwgbikgJT4lDQogIGZpbHRlcihuID4gNykgJT4lDQogIGdyYXBoX2Zyb21fZGF0YV9mcmFtZSgpDQojIHN0cihiaWdyYW1fZ3JhcGgpDQpgYGANCg0KYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwLCBtZXNzYWdlPVRSVUUsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGdncmFwaCkNCnNldC5zZWVkKDIwMTcpDQoNCmdncmFwaChiaWdyYW1fZ3JhcGgsIGxheW91dCA9ICJmciIpICsNCiAgZ2VvbV9lZGdlX2xpbmsoKSArDQogIGdlb21fbm9kZV9wb2ludCgpICsNCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIHZqdXN0ID0gMSwgaGp1c3QgPSAxKQ0KYGBgDQpgYGB7cn0NCmEgPC0gZ3JpZDo6YXJyb3codHlwZSA9ICJjbG9zZWQiLCBsZW5ndGggPSB1bml0KC4xNSwgImluY2hlcyIpKQ0KDQpnZ3JhcGgoYmlncmFtX2dyYXBoLCBsYXlvdXQgPSAiZnIiKSArDQogIGdlb21fZWRnZV9saW5rKGFlcyhlZGdlX2FscGhhID0gbiksIHNob3cubGVnZW5kID0gRkFMU0UsDQogICAgICAgICAgICAgICAgIGFycm93ID0gYSwgZW5kX2NhcCA9IGNpcmNsZSguMDcsICdpbmNoZXMnKSkgKw0KICBnZW9tX25vZGVfcG9pbnQoY29sb3IgPSAibGlnaHRibHVlIiwgc2l6ZSA9IDUpICsNCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIHZqdXN0ID0gMSwgaGp1c3QgPSAxKSArDQogIHRoZW1lX3ZvaWQoKQ0KYGBgDQoNCiMjIyMNCg0KVGFtYmnpbiBwb2RlbW9zIHJlYWxpemFyIHRmX2lkZiBzb2JyZSBudWVzdHJvIGNvcnB1cyBwYXJhIGxvY2FsaXphciBsYXMgcGFsYWJyYXMgbeFzIHJlbGV2YW50ZXMsIGVuIGdlbmVyYWwgeSBwb3IgdGVtcG9yYWRhDQoNCmBgYHtyIHRmX2lkZn0NCmxpYnJhcnkoZHBseXIpDQp0Zl9pZGZfZGYgPC0gdGlkeV9kZiAlPiUgDQogICAgICAgIGNvdW50KHNlYXNvbiwgd29yZCwgc29ydCA9IFRSVUUpICU+JQ0KICAgICAgICBiaW5kX3RmX2lkZih3b3JkLCBzZWFzb24sIG4pDQp0Zl9pZGZfZGYgPC0gdGZfaWRmX2RmW29yZGVyKC10Zl9pZGZfZGYkdGZfaWRmKSxdIA0KYGBgDQoNCmBgYHtyIHRmX2lkZjJ9DQp0Zl9pZGZfZGYgJT4lIA0KICB0b3BfbigyMCkgJT4lDQogIGdncGxvdChhZXMod29yZCwgdGZfaWRmLCBmaWxsID0gc2Vhc29uKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgbGFicyh4ID0gTlVMTCwgeSA9ICJ0Zi1pZGYiKSArDQogIGNvb3JkX2ZsaXAoKQ0KYGBgDQpgYGB7ciBieXNlYXNvbiwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OCx9DQp0Zl9pZGZfZGYgJT4lIA0KICBncm91cF9ieShzZWFzb24pICU+JSANCiAgdG9wX24oOCkgJT4lIA0KICB1bmdyb3VwICU+JQ0KICBnZ3Bsb3QoYWVzKHJlb3JkZXIod29yZCwgdGZfaWRmKSwgdGZfaWRmLCBmaWxsID0gc2Vhc29uKSkgKw0KICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGxhYnMoeCA9IE5VTEwsIHkgPSAidGYtaWRmIikgKw0KICBmYWNldF93cmFwKH5zZWFzb24sIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZSIpICsNCiAgY29vcmRfZmxpcCgpDQpgYGANCg0KIyMjIyBBbuFsaXNpcyBkZSBzZW50aW1pZW50bw0KDQpWYWxvcmFyIGNvbW8gcG9zaXRpdm8gbyBuZWdhdGl2byB1biBtZW5zYWplIGVzIHVuYSB0YXJlYSBjb21wbGVqYSwgcXVlIHJlcXVpZXJlIG5vIHPzbG8gY29ub2NlciBlbCBfX3NpZ25pZmljYWRvX18gZGUgbGFzIHBhbGFicmFzIHNpbm8gdGFtYmnpbiBjb250ZXh0dWFsaXphcmxhcywgY29ub2NlciBsYSBlbnRvbmFjafNuIGVuIHF1ZSBzZSBwcm9kdWNlIGVsIG1lbnNhamUsIGV0Yy4gDQoNCkVuIGVzdGUgY2FzbyB2YW1vcyBhIHJlYWxpemFyIHVuYSBhcHJveGltYWNp824gbXVjaG8gbeFzIHNpbXBsZSwgcXVlIGVzIGxhIGRlIGNvbnNpZGVyYXIgZWwgdGV4dG8gY29tbyBsYSBjb21iaW5hY2nzbiBkZSBwYWxhYnJhcyBpbmRpdmlkdWFsZXMgeSBlbCBzZW50aW1pZW50byBjb21vIGxhIHN1bWEgZGVsIHNlbnRpbWllbnRvIGFzb2NpYWRvIGEgY2FkYSB1bmEgZGUgbGFzIHBhbGFicmFzLiBQYXJhIGVsbG8gYGBgdGlkeXRleHRgYGBub3Mgb2ZyZWNlIHRyZXMgcG9zaWJsZXMgZGF0YXNldHMgKGxleGljb24pIGRlIHNlbnRpbWllbnRvczoNCisgQUZJTk4gZnJvbSBGaW5uIMVydXAgTmllbHNlbiAoLTUsIDUpLA0KKyBiaW5nIGZyb20gQmluZyBMaXUgYW5kIGNvbGxhYm9yYXRvcnMoInBvc2l0aXZlIiAvICJuZWdhdGl2ZSIpDQorIG5yYyBmcm9tIFNhaWYgTW9oYW1tYWQgYW5kIFBldGVyIFR1cm5leSAoInllcyIgLyAibm8iKS4NCg0KVG9kb3MgZWxsb3MgYmFzYWRvcyBlbiB1bmlncmFtYXMuIA0KDQoNCg0KYGBge3Igc2VudGltZW50X2FuYWx5c2lzLCBlY2hvPVRSVUV9DQpoZWFkKGdldF9zZW50aW1lbnRzKCJhZmlubiIpLCAzKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPVRSVUV9DQpoZWFkKGdldF9zZW50aW1lbnRzKCJiaW5nIiksIDMpDQpgYGANCmBgYHtyLCBlY2hvPVRSVUV9DQpoZWFkKGdldF9zZW50aW1lbnRzKCJucmMiKSwgMykNCmBgYA0KDQpgYGB7ciwgZWNobz1UUlVFfQ0KdGlkeV9kZlssIHNlYXNvbl9lcGlzb2RlIDo9IHBhc3RlMCgnUycsIHNlYXNvbl9udW0sIlhFIiwgc3RyX3BhZChlcGlzb2RlX251bSx3aWR0aCA9IDIscGFkID0gIjAiKSldDQpgYGANCg0KYGBge3IsIGVjaG89VFJVRX0NCmxpYnJhcnkodGlkeXIpDQoNCnNpbXBzb25fc2VudGltZW50IDwtIHRpZHlfZGYgJT4lDQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImJpbmciKSkgJT4lDQogIGNvdW50KHNlYXNvbl9lcGlzb2RlLCBzZW50aW1lbnQpICU+JQ0KICBzcHJlYWQoc2VudGltZW50LCBuLCBmaWxsID0gMCkgJT4lDQogIG11dGF0ZShzZW50aW1lbnQgPSBwb3NpdGl2ZSAtIG5lZ2F0aXZlKQ0KDQpoZWFkKHNpbXBzb25fc2VudGltZW50LDMpDQpgYGANCmBgYHtyICxmaWcuaGVpZ2h0PTEyLCBlY2hvPVRSVUUsIGZpZy53aWR0aD0xMn0NCnNpbXBzb25fc2VudGltZW50IDwtIGFzLmRhdGEudGFibGUoc2ltcHNvbl9zZW50aW1lbnQpDQpzaW1wc29uX3NlbnRpbWVudFssIHNlYXNvbjo9IHN0cl9zdWIoc2Vhc29uX2VwaXNvZGUsc3RhcnQgPSAxLCBlbmQgPSAyKV0NCnNpbXBzb25fc2VudGltZW50Wywgc2Vhc29uOj0gc3RyX3JlcGxhY2Uoc2Vhc29uLCAiUyIsICJTZWFzb24gIildDQpzaW1wc29uX3NlbnRpbWVudFssIGVwaXNvZGU6PSBzdHJfc3ViKHNlYXNvbl9lcGlzb2RlLHN0YXJ0ID0gNCwgZW5kID0gNildDQoNCmdncGxvdChzaW1wc29uX3NlbnRpbWVudCwgYWVzKGVwaXNvZGUsIHNlbnRpbWVudCwgZmlsbCA9IHNlYXNvbikpICsNCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBmYWNldF93cmFwKH5zZWFzb24sIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikNCmBgYA0KYGBge3IsIGVjaG89VFJVRSwgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTEyfQ0KYWZpbm4gPC0gdGlkeV9kZiAlPiUgDQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImFmaW5uIikpICU+JSANCiAgZ3JvdXBfYnkoc2Vhc29uX2VwaXNvZGUpICU+JSANCiAgc3VtbWFyaXNlKHNlbnRpbWVudCA9IHN1bShzY29yZSkpICU+JSANCiAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpDQoNCmFmaW5uIDwtIGFzLmRhdGEudGFibGUoYWZpbm4pDQphZmlublssIHNlYXNvbjo9IHN0cl9zdWIoc2Vhc29uX2VwaXNvZGUsc3RhcnQgPSAxLCBlbmQgPSAyKV0NCmFmaW5uWywgc2Vhc29uOj0gc3RyX3JlcGxhY2Uoc2Vhc29uLCAiUyIsICJTZWFzb24gIildDQphZmlublssIGVwaXNvZGU6PSBzdHJfc3ViKHNlYXNvbl9lcGlzb2RlLHN0YXJ0ID0gNCwgZW5kID0gNildDQoNCmdncGxvdChhZmlubiwgYWVzKGVwaXNvZGUsIHNlbnRpbWVudCwgZmlsbCA9IHNlYXNvbikpICsNCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBmYWNldF93cmFwKH5zZWFzb24sIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikNCg0KYGBgDQoNCmBgYHtyLCBlY2hvPVRSVUV9DQoNCnJlc3BsYW5kaW9yIDwtIHRpZHlfZGZbc2Vhc29uX251bT09NiAmIGVwaXNvZGVfbnVtPT02XQ0KYWZpbm5fciA8LSByZXNwbGFuZGlvciAlPiUgDQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImFmaW5uIikpICU+JSANCiAgZ3JvdXBfYnkoc2Vhc29uX2VwaXNvZGUpICU+JSANCiAgc3VtbWFyaXNlKHNlbnRpbWVudCA9IHN1bShzY29yZSkpICU+JSANCiAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpICU+JSBkYXRhLnRhYmxlKCkNCg0KYmluZ19hbmRfbnJjX3IgPC0gYmluZF9yb3dzKHJlc3BsYW5kaW9yICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShtZXRob2QgPSAiQmluZyBldCBhbC4iKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzcGxhbmRpb3IgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoIm5yYyIpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKHNlbnRpbWVudCAlaW4lIGMoInBvc2l0aXZlIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJuZWdhdGl2ZSIpKSkgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKG1ldGhvZCA9ICJOUkMiKSkgJT4lDQogIGNvdW50KG1ldGhvZCwgIHNlbnRpbWVudCkgJT4lDQogIHNwcmVhZChzZW50aW1lbnQsIG4sIGZpbGwgPSAwKSAlPiUNCiAgbXV0YXRlKHNlbnRpbWVudCA9IHBvc2l0aXZlIC0gbmVnYXRpdmUpICAlPiUgZGF0YS50YWJsZSgpDQoNCmNvbXBhcmF0aXZhIDwtIHJiaW5kKGFmaW5uX3JbLCBjKCJzZW50aW1lbnQiLCAibWV0aG9kIiksIHdpdGggPSBGXSwgYmluZ19hbmRfbnJjX3JbLCBjKCJzZW50aW1lbnQiLCAibWV0aG9kIiksIHdpdGggPSBGXSkNCg0KDQpnZ3Bsb3QoZGF0YT1jb21wYXJhdGl2YSwgYWVzKHg9bWV0aG9kLCB5PXNlbnRpbWVudCkpICsNCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLGZpbGw9InN0ZWVsYmx1ZSIpKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KYGBgDQoNCg0KDQojIyBBbmV4b3MgeSBvdHJhIGluZm9ybWFjafNuIPp0aWwNCg0KKyBUaWR5IHRleHQ6IGh0dHA6Ly90aWR5dGV4dG1pbmluZy5jb20NCisgU3RyaW5nciBhbmQgUmVnZXg6IGh0dHBzOi8vcnN0dWRpby1wdWJzLXN0YXRpYy5zMy5hbWF6b25hd3MuY29tLzE4MDYxMF9kMzc2NGM0M2YxZTU0NjkyYjdlODRkMjFlYzk0NzcyYS5odG1sDQorIEFuYWxpc2lzIFJpY2smTW9ydHk6IGh0dHA6Ly90YW1hc3ppbGFneWkuY29tL2Jsb2cvYS10aWR5LXRleHQtYW5hbHlzaXMtb2Ytcmljay1hbmQtbW9ydHkvDQorIEdncGxvdCBjaGVhdHNoZWV0OiBodHRwczovL3d3dy5yc3R1ZGlvLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNS8wMy9nZ3Bsb3QyLWNoZWF0c2hlZXQucGRmDQorIFBsb3RseSBjaGVhdHNoZWV0OiBodHRwczovL2ltYWdlcy5wbG90Lmx5L3Bsb3RseS1kb2N1bWVudGF0aW9uL2ltYWdlcy9yX2NoZWF0X3NoZWV0LnBkZg0KKyBDdXJzbyB0ZXh0IG1pbmluZyBEYXRhIGNhbXA6IGh0dHBzOi8vd3d3LmRhdGFjYW1wLmNvbS9jb3Vyc2VzL2ludHJvLXRvLXRleHQtbWluaW5nLWJhZy1vZi13b3Jkcw0KKyBTdWJ0b29sczogaHR0cDovL3d3dy5waWVjZW9may5mci8/cD00Mzc=